#include <QtCore/QtEndian>
#include <QtCore/QDateTime>
#include <QtCore/QTimer>
#include "kernel/kernel.h"
#include "kernel/kernel_p.h"
#ifdef WITH_ZABBIX
#include "kernel/zabbix_writer.h"
#endif

namespace smstranslator {

#ifdef WITH_ZABBIX

ZabbixWriter::ZabbixWriter(KernelPrivate *parent,
                           QHostAddress addr,
                           quint16 port) :
  parent(parent), addr(addr), port(port) {
  moveToThread(this);
}

ZabbixWriter::~ZabbixWriter() {

}

void ZabbixWriter::write(const QByteArray &msg) {
  QTcpSocket socket;
  socket.connectToHost(addr, port);
  if(!socket.waitForConnected(1000)) {
    if(parent->log_level >= log::WARNING) {
      parent->log_manager->warning(
        KERNEL_ID,
        KERNEL_ID,
        KERNEL_ID,
        QString("Error zabbix connection %1:%2 "
                "code (%3) message (%4)")
          .arg(addr.toString())
          .arg(port)
          .arg(socket.error())
          .arg(socket.errorString())
          .toUtf8());
    }
    return;
  }

  if(parent->log_level == log::DEBUG) {
    parent->log_manager->debug(KERNEL_ID,
                               KERNEL_ID,
                               KERNEL_ID,
                               QString("Connected zabbix server %1:%2->%3:%4")
                                 .arg(socket.localAddress().toString())
                                 .arg(socket.localPort())
                                 .arg(socket.peerAddress().toString())
                                 .arg(socket.peerPort())
                                 .toUtf8());
  }

  qint64 write_bytes(socket.write(msg));
  socket.flush();

  if(write_bytes < msg.size()) {
    if(!socket.waitForBytesWritten(1000)) {
      socket.close();
      if(parent->log_level >= log::WARNING) {
        parent->log_manager->warning(
          KERNEL_ID,
          KERNEL_ID,
          KERNEL_ID,
          QString("Error zabbix write %1:%2 "
                  "code (%3) message (%4)")
            .arg(addr.toString())
            .arg(port)
            .arg(socket.error())
            .arg(socket.errorString())
            .toUtf8());
      }
      return;
    }
  }

  while(socket.bytesAvailable() < 5) {
    if(!socket.waitForReadyRead(2000)) {
      socket.close();
      if(parent->log_level >= log::DEBUG) {
        parent->log_manager->warning(
          KERNEL_ID,
          KERNEL_ID,
          KERNEL_ID,
          QString("Error zabbix read header %1:%2 "
                  "code (%3) message (%4)")
            .arg(addr.toString())
            .arg(port)
            .arg(socket.error())
            .arg(socket.errorString())
            .toUtf8());
      }
      return;
    }
  }

  QByteArray data(socket.read(5));

  while(socket.bytesAvailable() < 8) {
    if(!socket.waitForReadyRead(2000)) {
      socket.close();
      if(parent->log_level >= log::WARNING) {
        parent->log_manager->warning(
          KERNEL_ID,
          KERNEL_ID,
          KERNEL_ID,
          QString("Error zabbix read message length %1:%2 "
                  "code (%3) message (%4)")
            .arg(addr.toString())
            .arg(port)
            .arg(socket.error())
            .arg(socket.errorString())
            .toUtf8());
      }
      return;
    }
  }

  data = socket.read(8);

  quint64 size(0);

  char *c_pt(reinterpret_cast<char *>(&size));

  c_pt[0] = data[0];
  c_pt[1] = data[1];
  c_pt[2] = data[2];
  c_pt[3] = data[3];
  c_pt[4] = data[4];
  c_pt[5] = data[5];
  c_pt[6] = data[6];
  c_pt[7] = data[7];

  while((quint64)socket.bytesAvailable() < size) {
    if(!socket.waitForReadyRead(2000)) {
      socket.abort();
      if(parent->log_level >= log::WARNING) {
        parent->log_manager->warning(
          KERNEL_ID,
          KERNEL_ID,
          KERNEL_ID,
          QString("Error zabbix read message %1:%2 "
                  "code (%3) message (%4)")
            .arg(addr.toString())
            .arg(port)
            .arg(socket.error())
            .arg(socket.errorString())
            .toUtf8());
      }
      return;
    }
  }

  data = socket.read(size);

  if(parent->log_level == log::DEBUG) {
    parent->log_manager->debug(
      KERNEL_ID,
      KERNEL_ID,
      KERNEL_ID,
      QString("Response (%1) from zabbix server %2:%3->%4:%5")
        .arg(QString::fromUtf8(data))
        .arg(socket.localAddress().toString())
        .arg(socket.localPort())
        .arg(socket.peerAddress().toString())
        .arg(socket.peerPort())
        .toUtf8());
  }

  socket.disconnectFromHost();

  if (socket.state() == QAbstractSocket::UnconnectedState ||
      socket.waitForDisconnected(1000)) {
    if(parent->log_level == log::DEBUG) {
      parent->log_manager->debug(KERNEL_ID,
                                 KERNEL_ID,
                                 KERNEL_ID,
                                 QString("Disconnected zabbix server %1:%2")
                                   .arg(addr.toString())
                                   .arg(port)
                                   .toUtf8());
    }
  } else {
    if(parent->log_level >= log::WARNING) {
      parent->log_manager->warning(
        KERNEL_ID,
        KERNEL_ID,
        KERNEL_ID,
        QString("Error zabbix disconnect %1:%2 "
                "code (%3) message (%4)")
          .arg(addr.toString())
          .arg(port)
          .arg(socket.error())
          .arg(socket.errorString())
          .toUtf8());
    }
  }
}

#endif

KernelPrivate::KernelPrivate(Kernel *parent, log::LogManager *log_manager) :
  QObject(0),
  parent(parent),
  log_level(log::WARNING),
  server(new SourceServer(this)),
  server_port(0),
  smsc_port(0),
  state(STOPED),
  log_manager(log_manager),
  stat_file(0),
  stat_time(1000),
  stat_timer(-1),
#ifdef WITH_ZABBIX
  zabbix_time(-1),
  zabbix_timer(-1),
  zabbix_port(0),
  zabbix(0),
#endif
  counter_file(new QtJsonSettings(DEFAULT_COUNTER_PATH))
{
  connect(server, SIGNAL(newSession(int)), this, SLOT(newSrc(int)));
  connect(this, SIGNAL(sigStop()), this, SLOT(stop()));
  connect(this, SIGNAL(sigReloadConfig(QSettings *)),
          this, SLOT(reloadConfig(QSettings *)));
}

KernelPrivate::~KernelPrivate() {
  if(server) {
    delete server;
    server = 0;
  }
  if(!clients.isEmpty()) {
    qDeleteAll(clients);
    clients.clear();
  }
  if(stat_file) {
    stat_file->close();
    delete stat_file;
    stat_file = 0;
  }
}

void KernelPrivate::start() {
  setState(STARTING);
  checkState();
}

void KernelPrivate::stop() {
  setState(STOPING);
  checkState();
}

void KernelPrivate::timerEvent(QTimerEvent *event) {
  int timer_id(event->timerId());
  if(stat_timer == timer_id) {
    writeStat();
  }
#ifdef WITH_ZABBIX
  else if(zabbix_timer == timer_id) {
    writeZabbix();
  }
#endif
}

void KernelPrivate::newSrc(int socket_descriptor) {
  smpp::sessions::ServerAsyncSession *src_session(
    new smpp::sessions::ServerAsyncSession(socket_descriptor,
                                           DEF_MAX_IN_SPEED,
                                           DEF_MAX_OUT_SPEED,
                                           DEF_SESSION_INIT_TIME,
                                           DEF_ENQUIRE_LINK_TIME,
                                           DEF_INACTIVITY_TIME,
                                           DEF_TRANSACTION_TIME,
                                           DEF_IN_TRANSACTIONS,
                                           DEF_OUT_TRANSACTIONS));

  connect(src_session, SIGNAL(openedSession()),
          this, SLOT(openedSrc()));
  connect(src_session, SIGNAL(closedSession()),
          this, SLOT(closedSrc()));
  connect(src_session, SIGNAL(receive(const smpp::pdu::BindTransmitter &)),
          this, SLOT(receiveSrc(const smpp::pdu::BindTransmitter &)));
  connect(src_session, SIGNAL(receive(const smpp::pdu::BindReceiver &)),
          this, SLOT(receiveSrc(const smpp::pdu::BindReceiver &)));
  connect(src_session, SIGNAL(receive(const smpp::pdu::BindTransceiver &)),
          this, SLOT(receiveSrc(const smpp::pdu::BindTransceiver &)));
  connect(src_session, SIGNAL(receive(const smpp::pdu::SubmitSm &)),
          this, SLOT(receiveSrc(const smpp::pdu::SubmitSm &)));
  connect(src_session, SIGNAL(receive(const smpp::pdu::QuerySm &)),
          this, SLOT(receiveSrc(const smpp::pdu::QuerySm &)));
  connect(src_session, SIGNAL(receive(const smpp::pdu::ReplaceSm &)),
          this, SLOT(receiveSrc(const smpp::pdu::ReplaceSm &)));
  connect(src_session, SIGNAL(receive(const smpp::pdu::CancelSm &)),
          this, SLOT(receiveSrc(const smpp::pdu::CancelSm &)));
  connect(src_session, SIGNAL(receive(const smpp::pdu::EnquireLink &)),
          this, SLOT(receiveSrc(const smpp::pdu::EnquireLink &)));
  connect(src_session, SIGNAL(sessionInitTimeout()),
          this, SLOT(sessionInitSrc()));
  connect(src_session, SIGNAL(error(smpp::sessions::ErrorCode)),
          this, SLOT(errorSrc(smpp::sessions::ErrorCode)));

  smpp::sessions::ClientAsyncSession *dest_session(
    new smpp::sessions::ClientAsyncSession(DEF_MAX_IN_SPEED,
                                           DEF_MAX_OUT_SPEED,
                                           DEF_SESSION_INIT_TIME,
                                           DEF_ENQUIRE_LINK_TIME,
                                           DEF_INACTIVITY_TIME,
                                           DEF_TRANSACTION_TIME,
                                           DEF_IN_TRANSACTIONS,
                                           DEF_OUT_TRANSACTIONS));

  connect(dest_session, SIGNAL(sessionInitTimeout()),
          this, SLOT(sessionInitDest()));
  connect(dest_session, SIGNAL(error(smpp::sessions::ErrorCode)),
          this, SLOT(errorDest(smpp::sessions::ErrorCode)));
  connect(src_session, SIGNAL(receive(const smpp::pdu::Unbind &)),
          dest_session, SLOT(send(const smpp::pdu::Unbind &)));
  connect(src_session, SIGNAL(receive(const smpp::pdu::DeliverSmResp &)),
          dest_session, SLOT(send(const smpp::pdu::DeliverSmResp &)));
  connect(src_session, SIGNAL(receive(const smpp::pdu::EnquireLinkResp &)),
          dest_session, SLOT(send(const smpp::pdu::EnquireLinkResp &)));
  connect(src_session, SIGNAL(inStatus(const smpp::pdu::Header &,
                                   smpp::sessions::StatusMessage)),
          this, SLOT(inStatusSrc(const smpp::pdu::Header &,
                                 smpp::sessions::StatusMessage)));

  connect(dest_session, SIGNAL(openedSession()),
          this, SLOT(openedDest()));
  connect(dest_session, SIGNAL(closedSession()),
          this, SLOT(closedDest()));
  connect(dest_session, SIGNAL(receive(const smpp::pdu::BindTransmitterResp &)),
          this, SLOT(receiveDest(const smpp::pdu::BindTransmitterResp &)));
  connect(dest_session, SIGNAL(receive(const smpp::pdu::BindReceiverResp &)),
          this, SLOT(receiveDest(const smpp::pdu::BindReceiverResp &)));
  connect(dest_session, SIGNAL(receive(const smpp::pdu::BindTransceiverResp &)),
          this, SLOT(receiveDest(const smpp::pdu::BindTransceiverResp &)));
  connect(dest_session, SIGNAL(receive(const smpp::pdu::UnbindResp &)),
          this, SLOT(receiveDest(const smpp::pdu::UnbindResp &)));
  connect(dest_session, SIGNAL(receive(const smpp::pdu::SubmitSmResp &)),
          src_session, SLOT(send(const smpp::pdu::SubmitSmResp &)));
  connect(dest_session, SIGNAL(receive(const smpp::pdu::DeliverSm &)),
          this, SLOT(receiveDest(const smpp::pdu::DeliverSm &)));
  connect(dest_session, SIGNAL(receive(const smpp::pdu::EnquireLink &)),
          src_session, SLOT(send(const smpp::pdu::EnquireLink &)));
  connect(dest_session, SIGNAL(receive(const smpp::pdu::EnquireLinkResp &)),
          src_session, SLOT(send(const smpp::pdu::EnquireLinkResp &)));

  linked_sessions.insert(src_session, dest_session);
  packets_on_close.insert(src_session, QList<smpp::pdu::Header *>());
}

void KernelPrivate::openedDest() {
  smpp::sessions::ClientAsyncSession *dest_session =
    qobject_cast<smpp::sessions::ClientAsyncSession *>(sender());

  if(!dest_session) {
    return;
  }

  if(log_level >= log::INFO) {
    log_manager->info(KERNEL_ID,
                      KERNEL_ID,
                      KERNEL_ID,
                      QString("Open destination session %1:%2->%3:%4")
                        .arg(dest_session->localAddress().toString())
                        .arg(dest_session->localPort())
                        .arg(dest_session->peerAddress().toString())
                        .arg(dest_session->peerPort())
                        .toUtf8());
  }

  smpp::sessions::ServerAsyncSession *src_session =
    linked_sessions.key(dest_session, 0);

  if(!src_session) {
    return;
  }

  while(!packets_on_close[src_session].isEmpty()) {
    smpp::pdu::Header *header = packets_on_close[src_session].takeFirst();
    switch(header->getCommandId()) {
      case smpp::parameters::command_id::ENQUIRE_LINK: {
        smpp::pdu::EnquireLink *p =
          reinterpret_cast<smpp::pdu::EnquireLink *>(header);
        dest_session->send(smpp::pdu::EnquireLink(*p));
        delete p;
        break;
      }
      case smpp::parameters::command_id::BIND_RECEIVER: {
        smpp::pdu::BindReceiver *p =
          reinterpret_cast<smpp::pdu::BindReceiver *>(header);
        dest_session->send(smpp::pdu::BindReceiver(*p));
        delete p;
        break;
      }
      case smpp::parameters::command_id::BIND_TRANSCEIVER: {
        smpp::pdu::BindTransceiver *p =
          reinterpret_cast<smpp::pdu::BindTransceiver *>(header);
        dest_session->send(smpp::pdu::BindTransceiver(*p));
        delete p;
        break;
      }
      case smpp::parameters::command_id::BIND_TRANSMITTER: {
        smpp::pdu::BindTransmitter *p =
          reinterpret_cast<smpp::pdu::BindTransmitter *>(header);
        dest_session->send(smpp::pdu::BindTransmitter(*p));
        delete p;
        break;
      }
      default:
        break;
    }
  }
}

void KernelPrivate::closedDest() {
  smpp::sessions::ClientAsyncSession *dest_session =
    qobject_cast<smpp::sessions::ClientAsyncSession *>(sender());

  if(!dest_session) {
    return;
  }

  smpp::sessions::ServerAsyncSession *src_session =
    linked_sessions.key(dest_session, 0);

  if(!src_session) {
    return;
  }

  if(log_level >= log::INFO) {
    log_manager->info(KERNEL_ID,
                      KERNEL_ID,
                      KERNEL_ID,
                      QString("Close destination session %1:%2->%3:%4")
                        .arg(dest_session->localAddress().toString())
                        .arg(dest_session->localPort())
                        .arg(dest_session->peerAddress().toString())
                        .arg(dest_session->peerPort())
                        .toUtf8());
  }

  if(src_session->state() == smpp::sessions::CLOSED) {
    if(linked_sessions.remove(src_session) > 0) {
      sid_sessions.remove(src_session);
      client_sessions.remove(src_session);
      clearPacketsOnClose(src_session);
      delete src_session;
      delete dest_session;
    }
  } else {
    src_session->closeSession();
  }

  checkState();
}

void KernelPrivate::receiveDest(const smpp::pdu::BindTransmitterResp &p) {
  smpp::sessions::ClientAsyncSession *dest_session =
    qobject_cast<smpp::sessions::ClientAsyncSession *>(sender());

  if(!dest_session) {
    return;
  }

  smpp::sessions::ServerAsyncSession *src_session =
    linked_sessions.key(dest_session, 0);

  if(!src_session) {
    return;
  }

  if(p.getCommandStatus() == smpp::parameters::command_status::ESME_ROK) {
    Client *client = client_sessions.value(src_session, 0);

    if(client) {
      if(log_level >= log::INFO) {
        log_manager->info(KERNEL_ID,
                          KERNEL_ID,
                          KERNEL_ID,
                          QString("Success bind transmitter %1:%2->%3:%4 "
                                  "on SID %5")
                            .arg(src_session->peerAddress().toString())
                            .arg(src_session->peerPort())
                            .arg(src_session->localAddress().toString())
                            .arg(src_session->localPort())
                            .arg(sid_sessions.value(src_session, QString()))
                            .toUtf8());
      }

      src_session->setMaxInSpeed(client->max_in_speed);
      src_session->setMaxOutSpeed(client->max_out_speed);
      src_session->setMaxInTrans(client->max_in_trans);
      src_session->setMaxOutTrans(client->max_out_trans);
      dest_session->setMaxInSpeed(client->max_out_speed);
      dest_session->setMaxOutSpeed(client->max_in_speed);
      dest_session->setMaxInTrans(client->max_out_trans);
      dest_session->setMaxOutTrans(client->max_in_trans);
    }
  } else {
    if(log_level >= log::INFO) {
      log_manager->info(KERNEL_ID,
                        KERNEL_ID,
                        KERNEL_ID,
                        QString("Fault bind transmitter %1:%2->%3:%4 "
                                "on SID %5 command status %6")
                          .arg(src_session->peerAddress().toString())
                          .arg(src_session->peerPort())
                          .arg(src_session->localAddress().toString())
                          .arg(src_session->localPort())
                          .arg(sid_sessions.value(src_session, QString()))
                          .arg(p.getCommandStatus())
                          .toUtf8());
    }
    sid_sessions.remove(src_session);
    client_sessions.remove(src_session);
  }

  src_session->send(p);
}

void KernelPrivate::receiveDest(const smpp::pdu::BindReceiverResp &p) {
  smpp::sessions::ClientAsyncSession *dest_session =
    qobject_cast<smpp::sessions::ClientAsyncSession *>(sender());

  if(!dest_session) {
    return;
  }

  smpp::sessions::ServerAsyncSession *src_session =
    linked_sessions.key(dest_session, 0);

  if(!src_session) {
    return;
  }

  if(p.getCommandStatus() == smpp::parameters::command_status::ESME_ROK) {
    Client *client = client_sessions.value(src_session, 0);

    if(client) {
      if(log_level >= log::INFO) {
        log_manager->info(KERNEL_ID,
                          KERNEL_ID,
                          KERNEL_ID,
                          QString("Success bind receiver %1:%2->%3:%4 "
                                  "on SID %5")
                            .arg(src_session->peerAddress().toString())
                            .arg(src_session->peerPort())
                            .arg(src_session->localAddress().toString())
                            .arg(src_session->localPort())
                            .arg(sid_sessions.value(src_session, QString()))
                            .toUtf8());
      }

      src_session->setMaxInSpeed(client->max_in_speed);
      src_session->setMaxOutSpeed(client->max_out_speed);
      src_session->setMaxInTrans(client->max_in_trans);
      src_session->setMaxOutTrans(client->max_out_trans);
      dest_session->setMaxInSpeed(client->max_out_speed);
      dest_session->setMaxOutSpeed(client->max_in_speed);
      dest_session->setMaxInTrans(client->max_out_trans);
      dest_session->setMaxOutTrans(client->max_in_trans);
    }
  } else {
    if(log_level >= log::INFO) {
      log_manager->info(KERNEL_ID,
                        KERNEL_ID,
                        KERNEL_ID,
                        QString("Fault bind receiver %1:%2->%3:%4 "
                                "on SID %5 command status %6")
                          .arg(src_session->peerAddress().toString())
                          .arg(src_session->peerPort())
                          .arg(src_session->localAddress().toString())
                          .arg(src_session->localPort())
                          .arg(sid_sessions.value(src_session, QString()))
                          .arg(p.getCommandStatus())
                          .toUtf8());
    }
    sid_sessions.remove(src_session);
    client_sessions.remove(src_session);
  }

  src_session->send(p);
}

void KernelPrivate::receiveDest(const smpp::pdu::BindTransceiverResp &p) {
  smpp::sessions::ClientAsyncSession *dest_session =
    qobject_cast<smpp::sessions::ClientAsyncSession *>(sender());

  if(!dest_session) {
    return;
  }

  smpp::sessions::ServerAsyncSession *src_session =
    linked_sessions.key(dest_session, 0);

  if(!src_session) {
    return;
  }

  if(p.getCommandStatus() == smpp::parameters::command_status::ESME_ROK) {
    Client *client = client_sessions.value(src_session, 0);

    if(client) {
      if(log_level >= log::INFO) {
        log_manager->info(KERNEL_ID,
                          KERNEL_ID,
                          KERNEL_ID,
                          QString("Success bind transceiver %1:%2->%3:%4 "
                                  "on SID %5")
                            .arg(src_session->peerAddress().toString())
                            .arg(src_session->peerPort())
                            .arg(src_session->localAddress().toString())
                            .arg(src_session->localPort())
                            .arg(sid_sessions.value(src_session, QString()))
                            .toUtf8());
      }

      src_session->setMaxInSpeed(client->max_in_speed);
      src_session->setMaxOutSpeed(client->max_out_speed);
      src_session->setMaxInTrans(client->max_in_trans);
      src_session->setMaxOutTrans(client->max_out_trans);
      dest_session->setMaxInSpeed(client->max_out_speed);
      dest_session->setMaxOutSpeed(client->max_in_speed);
      dest_session->setMaxInTrans(client->max_out_trans);
      dest_session->setMaxOutTrans(client->max_in_trans);
    }
  } else {
    if(log_level >= log::INFO) {
      log_manager->info(KERNEL_ID,
                        KERNEL_ID,
                        KERNEL_ID,
                        QString("Fault bind transceiver %1:%2->%3:%4 "
                                "on SID %5 command status %6")
                          .arg(src_session->peerAddress().toString())
                          .arg(src_session->peerPort())
                          .arg(src_session->localAddress().toString())
                          .arg(src_session->localPort())
                          .arg(sid_sessions.value(src_session, QString()))
                          .arg(p.getCommandStatus())
                          .toUtf8());
    }
    sid_sessions.remove(src_session);
    client_sessions.remove(src_session);
  }

  src_session->send(p);
}

void KernelPrivate::receiveDest(const smpp::pdu::UnbindResp &p) {
  smpp::sessions::ClientAsyncSession *dest_session =
    qobject_cast<smpp::sessions::ClientAsyncSession *>(sender());

  if(!dest_session) {
    return;
  }

  smpp::sessions::ServerAsyncSession *src_session =
    linked_sessions.key(dest_session, 0);

  if(!src_session) {
    return;
  }

  sid_sessions.remove(src_session);
  client_sessions.remove(src_session);

  src_session->send(p);
}

void KernelPrivate::sessionInitDest() {
  smpp::sessions::ClientAsyncSession *dest_session =
    qobject_cast<smpp::sessions::ClientAsyncSession *>(sender());

  if(!dest_session) {
    return;
  }

  if(log_level >= log::INFO) {
    log_manager->info(KERNEL_ID,
                      KERNEL_ID,
                      KERNEL_ID,
                      QString("Destination session %1:%2->%3:%4 init timeout")
                        .arg(dest_session->peerAddress().toString())
                        .arg(dest_session->peerPort())
                        .arg(dest_session->localAddress().toString())
                        .arg(dest_session->localPort())
                        .toUtf8());
  }
}

void KernelPrivate::errorDest(smpp::sessions::ErrorCode code) {
  smpp::sessions::ClientAsyncSession *dest_session =
    qobject_cast<smpp::sessions::ClientAsyncSession *>(sender());

  if(!dest_session) {
    return;
  }

  smpp::sessions::ServerAsyncSession *src_session =
    linked_sessions.key(dest_session, 0);

  if(!src_session) {
    return;
  }

  if(log_level >= log::WARNING) {
    log_manager->warning(KERNEL_ID,
                         KERNEL_ID,
                         KERNEL_ID,
                         QString("Destination session %1:%2->%3:%4 "
                                 "error code (%5) message (%6)")
                           .arg(dest_session->localAddress().toString())
                           .arg(dest_session->localPort())
                           .arg(dest_session->peerAddress().toString())
                           .arg(dest_session->peerPort())
                           .arg(code)
                           .arg(dest_session->errorString())
                           .toUtf8());
  }

  if(code == smpp::sessions::CONN_SOCK_ERR ||
     code == smpp::sessions::DISC_SOCK_ERR) {
    if(src_session->state() == smpp::sessions::CLOSED) {
      if(linked_sessions.remove(src_session) > 0) {
        sid_sessions.remove(src_session);
        client_sessions.remove(src_session);
        clearPacketsOnClose(src_session);
        delete src_session;
        delete dest_session;
      }
    } else {
      src_session->closeSession();
    }

    checkState();
  }
}

void KernelPrivate::openedSrc() {
  smpp::sessions::ServerAsyncSession *src_session =
    qobject_cast<smpp::sessions::ServerAsyncSession *>(sender());

  if(!src_session) {
    return;
  }

  smpp::sessions::ClientAsyncSession *dest_session =
    linked_sessions.value(src_session, 0);

  if(!dest_session) {
    return;
  }

  QString ip(src_session->peerAddress().toString());

  for(int i = 0; i < clients.size(); i++) {
    Client *client = clients[i];
    if(client->access_ip.exactMatch(ip)) {
      if(log_level >= log::INFO) {
        log_manager->info(KERNEL_ID,
                          KERNEL_ID,
                          KERNEL_ID,
                          QString("Open source session %1:%2->%3:%4")
                            .arg(src_session->peerAddress().toString())
                            .arg(src_session->peerPort())
                            .arg(src_session->localAddress().toString())
                            .arg(src_session->localPort())
                            .toUtf8());
      }
      return dest_session->openSession(smsc_addr, smsc_port);
    }
  }

  if(log_level >= log::INFO) {
    log_manager->info(KERNEL_ID,
                      KERNEL_ID,
                      KERNEL_ID,
                      QString("Opened source session %1:%2->%3:%4 is unsuccess")
                        .arg(src_session->peerAddress().toString())
                        .arg(src_session->peerPort())
                        .arg(src_session->localAddress().toString())
                        .arg(src_session->localPort())
                        .toUtf8());
  }

  src_session->closeSession();
}

void KernelPrivate::closedSrc() {
  smpp::sessions::ServerAsyncSession *src_session =
    qobject_cast<smpp::sessions::ServerAsyncSession *>(sender());

  if(!src_session) {
    return;
  }

  sid_client_sessions.remove(src_session);

  smpp::sessions::ClientAsyncSession *dest_session =
    linked_sessions.value(src_session, 0);

  if(!dest_session) {
    return;
  }

  if(log_level >= log::INFO) {
    log_manager->info(KERNEL_ID,
                      KERNEL_ID,
                      KERNEL_ID,
                      QString("Close source session %1:%2->%3:%4")
                        .arg(src_session->peerAddress().toString())
                        .arg(src_session->peerPort())
                        .arg(src_session->localAddress().toString())
                        .arg(src_session->localPort())
                        .toUtf8());
  }

  if(dest_session->state() == smpp::sessions::CLOSED) {
    if(linked_sessions.remove(src_session) > 0) {
      sid_sessions.remove(src_session);
      client_sessions.remove(src_session);
      clearPacketsOnClose(src_session);
      delete src_session;
      delete dest_session;
    }
  } else {
    dest_session->closeSession();
  }

  checkState();
}

void KernelPrivate::receiveSrc(const smpp::pdu::BindTransmitter &p) {
  smpp::sessions::ServerAsyncSession *src_session =
    qobject_cast<smpp::sessions::ServerAsyncSession *>(sender());

  if(!src_session) {
    return;
  }

  /*if(sid_sessions.contains(src_session)) {
    if(log_level >= log::INFO) {
      log_manager->info(KERNEL_ID,
                        KERNEL_ID,
                        KERNEL_ID,
                        QString("Fault bind transmitter %1:%2->%3:%4 "
                                "on SID %5 command status %6")
                          .arg(src_session->peerAddress().toString())
                          .arg(src_session->peerPort())
                          .arg(src_session->localAddress().toString())
                          .arg(src_session->localPort())
                          .arg(p.getSystemId())
                          .arg(smpp::parameters::command_status::ESME_RALYBND)
                          .toUtf8());
    }
    src_session->send(smpp::pdu::BindTransmitterResp(
      smpp::parameters::command_status::ESME_RALYBND,
      p.getSequenceNumber(),
      p.getSystemId()));
    return;
  }*/

  QString ip(src_session->peerAddress().toString());
  QString sid(p.getSystemId());

  for(int i = 0; i < clients.size(); i++) {
    Client *client = clients[i];
    if(client->access_ip.exactMatch(ip) && client->access_sid.exactMatch(sid)) {
      smpp::sessions::ClientAsyncSession *dest_session =
        linked_sessions.value(src_session, 0);

      if(!dest_session) {
        return;
      }

      sid_sessions.insert(src_session, sid);
      client_sessions.insert(src_session, client);

      if(dest_session->state() == smpp::sessions::CLOSED) {
        packets_on_close[src_session].push_back(
          new smpp::pdu::BindTransmitter(p));
      } else {
        dest_session->send(p);
      }

      sid_client_sessions.insert(src_session, p.getSystemId());

      return;
    }
  }

  if(log_level >= log::INFO) {
    log_manager->info(KERNEL_ID,
                      KERNEL_ID,
                      KERNEL_ID,
                      QString("Fault bind transmitter %1:%2->%3:%4 "
                              "on SID %5 command status %6")
                        .arg(src_session->peerAddress().toString())
                        .arg(src_session->peerPort())
                        .arg(src_session->localAddress().toString())
                        .arg(src_session->localPort())
                        .arg(sid)
                        .arg(smpp::parameters::command_status::ESME_RINVSYSID)
                        .toUtf8());
  }

  src_session->send(smpp::pdu::BindTransmitterResp(
    smpp::parameters::command_status::ESME_RINVSYSID,
    p.getSequenceNumber(),
    p.getSystemId()));
}

void KernelPrivate::receiveSrc(const smpp::pdu::BindReceiver &p) {
  smpp::sessions::ServerAsyncSession *src_session =
    qobject_cast<smpp::sessions::ServerAsyncSession *>(sender());

  if(!src_session) {
    return;
  }

  /*if(sid_sessions.contains(src_session)) {
    if(log_level >= log::INFO) {
      log_manager->info(KERNEL_ID,
                        KERNEL_ID,
                        KERNEL_ID,
                        QString("Fault bind receiver %1:%2->%3:%4 "
                                "on SID %5 command status %6")
                          .arg(src_session->peerAddress().toString())
                          .arg(src_session->peerPort())
                          .arg(src_session->localAddress().toString())
                          .arg(src_session->localPort())
                          .arg(p.getSystemId())
                          .arg(smpp::parameters::command_status::ESME_RALYBND)
                          .toUtf8());
    }

    src_session->send(smpp::pdu::BindReceiverResp(
      smpp::parameters::command_status::ESME_RALYBND,
      p.getSequenceNumber(),
      p.getSystemId()));
    return;
  }*/

  QString ip(src_session->peerAddress().toString());
  QString sid(p.getSystemId());

  for(int i = 0; i < clients.size(); i++) {
    Client *client = clients[i];
    if(client->access_ip.exactMatch(ip) && client->access_sid.exactMatch(sid)) {
      smpp::sessions::ClientAsyncSession *dest_session =
        linked_sessions.value(src_session, 0);

      if(!dest_session) {
        return;
      }

      sid_sessions.insert(src_session, sid);
      client_sessions.insert(src_session, client);

      if(dest_session->state() == smpp::sessions::CLOSED) {
        packets_on_close[src_session].push_back(
          new smpp::pdu::BindReceiver(p));
      } else {
        dest_session->send(p);
      }
      return;
    }
  }

  if(log_level >= log::INFO) {
    log_manager->info(KERNEL_ID,
                      KERNEL_ID,
                      KERNEL_ID,
                      QString("Fault bind receiver %1:%2->%3:%4 "
                              "on SID %5 command status %6")
                        .arg(src_session->peerAddress().toString())
                        .arg(src_session->peerPort())
                        .arg(src_session->localAddress().toString())
                        .arg(src_session->localPort())
                        .arg(sid)
                        .arg(smpp::parameters::command_status::ESME_RINVSYSID)
                        .toUtf8());
  }

  src_session->send(smpp::pdu::BindReceiverResp(
    smpp::parameters::command_status::ESME_RINVSYSID,
    p.getSequenceNumber(),
    p.getSystemId()));
}

void KernelPrivate::receiveSrc(const smpp::pdu::BindTransceiver &p) {
  smpp::sessions::ServerAsyncSession *src_session =
    qobject_cast<smpp::sessions::ServerAsyncSession *>(sender());

  if(!src_session) {
    return;
  }

  /*if(sid_sessions.contains(src_session)) {
    if(log_level >= log::INFO) {
      log_manager->info(KERNEL_ID,
                        KERNEL_ID,
                        KERNEL_ID,
                        QString("Fault bind transceiver %1:%2->%3:%4 "
                                "on SID %5 command status %6")
                          .arg(src_session->peerAddress().toString())
                          .arg(src_session->peerPort())
                          .arg(src_session->localAddress().toString())
                          .arg(src_session->localPort())
                          .arg(p.getSystemId())
                          .arg(smpp::parameters::command_status::ESME_RALYBND)
                          .toUtf8());
    }

    src_session->send(smpp::pdu::BindTransceiverResp(
      smpp::parameters::command_status::ESME_RALYBND,
      p.getSequenceNumber(),
      p.getSystemId()));
    return;
  }*/

  QString ip(src_session->peerAddress().toString());
  QString sid(p.getSystemId());

  for(int i = 0; i < clients.size(); i++) {
    Client *client = clients[i];
    if(client->access_ip.exactMatch(ip) && client->access_sid.exactMatch(sid)) {
      smpp::sessions::ClientAsyncSession *dest_session =
        linked_sessions.value(src_session, 0);

      if(!dest_session) {
        return;
      }

      sid_sessions.insert(src_session, sid);
      client_sessions.insert(src_session, client);

      if(dest_session->state() == smpp::sessions::CLOSED) {
        packets_on_close[src_session].push_back(
          new smpp::pdu::BindTransceiver(p));
      } else {
        dest_session->send(p);
      }
      return;
    }
  }

  if(log_level >= log::INFO) {
    log_manager->info(KERNEL_ID,
                      KERNEL_ID,
                      KERNEL_ID,
                      QString("Fault bind transceiver %1:%2->%3:%4 "
                              "on SID %5 command status %6")
                        .arg(src_session->peerAddress().toString())
                        .arg(src_session->peerPort())
                        .arg(src_session->localAddress().toString())
                        .arg(src_session->localPort())
                        .arg(sid)
                        .arg(smpp::parameters::command_status::ESME_RINVSYSID)
                        .toUtf8());
  }

  src_session->send(smpp::pdu::BindTransceiverResp(
    smpp::parameters::command_status::ESME_RINVSYSID,
    p.getSequenceNumber(),
    p.getSystemId()));
}

void KernelPrivate::receiveSrc(const smpp::pdu::EnquireLink &p) {
  smpp::sessions::ServerAsyncSession *src_session =
    qobject_cast<smpp::sessions::ServerAsyncSession *>(sender());

  if(!src_session) {
    return;
  }

  smpp::sessions::ClientAsyncSession *dest_session =
    linked_sessions.value(src_session, 0);

  if(!dest_session) {
    return;
  }

  if(dest_session->state() == smpp::sessions::CLOSED) {
    packets_on_close[src_session].push_back(
      new smpp::pdu::EnquireLink(p));
  } else {
    dest_session->send(p);
  }
}

void KernelPrivate::receiveSrc(const smpp::pdu::SubmitSm &p) {
  smpp::sessions::ServerAsyncSession *src_session =
    qobject_cast<smpp::sessions::ServerAsyncSession *>(sender());

  if(!src_session) {
    return;
  }

  smpp::sessions::ClientAsyncSession *dest_session =
    linked_sessions.value(src_session, 0);

  if(!dest_session) {
    return;
  }

  quint8 source_ton(p.getSourceAddrTon());
  quint8 source_npi(p.getSourceAddrNpi());
  QString source_addr(p.getSourceAddr());
  QString dest_addr(p.getDestinationAddr());

  smpp::pdu::SubmitSm p_copy(p);

  for(int i = 0; i < table_translate.size(); i++) {
    TranslateReglation *reglation = table_translate[i];
    if(reglation->dest_addr.exactMatch(dest_addr) &&
       source_ton == reglation->before_ton &&
       source_npi == reglation->before_npi &&
       source_addr == reglation->before_addr) {
      p_copy.setSourceAddrTon(reglation->after_ton);
      p_copy.setSourceAddrNpi(reglation->after_npi);
      p_copy.setSourceAddr(reglation->after_addr);
      p_copy.setCommandLength(p.size());
    }
  }

  incrementSessionCounter(sid_client_sessions.value(src_session));

  dest_session->send(p_copy);
}

void KernelPrivate::receiveSrc(const smpp::pdu::QuerySm &p) {
  smpp::sessions::ServerAsyncSession *src_session =
    qobject_cast<smpp::sessions::ServerAsyncSession *>(sender());

  if(!src_session) {
    return;
  }

  src_session->send(smpp::pdu::QuerySmResp(
    smpp::parameters::command_status::ESME_RX_R_APPN,
    p.getSequenceNumber()));
}

void KernelPrivate::receiveSrc(const smpp::pdu::ReplaceSm &p) {
  smpp::sessions::ServerAsyncSession *src_session =
    qobject_cast<smpp::sessions::ServerAsyncSession *>(sender());

  if(!src_session) {
    return;
  }

  src_session->send(smpp::pdu::ReplaceSmResp(
    smpp::parameters::command_status::ESME_RX_R_APPN,
    p.getSequenceNumber()));
}

void KernelPrivate::receiveSrc(const smpp::pdu::CancelSm &p) {
  smpp::sessions::ServerAsyncSession *src_session =
    qobject_cast<smpp::sessions::ServerAsyncSession *>(sender());

  if(!src_session) {
    return;
  }

  src_session->send(smpp::pdu::CancelSmResp(
    smpp::parameters::command_status::ESME_RX_R_APPN,
    p.getSequenceNumber()));
}

void KernelPrivate::receiveDest(const smpp::pdu::DeliverSm &p) {
  smpp::sessions::ClientAsyncSession *dest_session =
    qobject_cast<smpp::sessions::ClientAsyncSession *>(sender());

  if(!dest_session) {
    return;
  }

  smpp::sessions::ServerAsyncSession *src_session =
    linked_sessions.key(dest_session, 0);

  if(!src_session) {
    return;
  }

  quint8 dest_ton(p.getDestAddrTon());
  quint8 dest_npi(p.getDestAddrNpi());
  QString dest_addr(p.getDestinationAddr());
  QString source_addr(p.getSourceAddr());

  smpp::pdu::DeliverSm p_copy(p);

  for(int i = 0; i < table_translate.size(); i++) {
    TranslateReglation *reglation = table_translate[i];
    if(reglation->dest_addr.exactMatch(source_addr) &&
       dest_ton == reglation->after_ton &&
       dest_npi == reglation->after_npi &&
       dest_addr == reglation->after_addr) {
      p_copy.setDestAddrTon(reglation->before_ton);
      p_copy.setDestAddrNpi(reglation->before_npi);
      p_copy.setDestinationAddr(reglation->before_addr);
      p_copy.setCommandLength(p.size());
    }
  }

  src_session->send(p_copy);
}

void KernelPrivate::inStatusSrc(const smpp::pdu::Header &header,
                                smpp::sessions::StatusMessage status) {
  smpp::sessions::ServerAsyncSession *session =
    qobject_cast<smpp::sessions::ServerAsyncSession *>(sender());

  if(!session) {
    return;
  }

  if(header.getCommandId() == smpp::parameters::command_id::SUBMIT_SM &&
     status == smpp::sessions::MAXSPEED) {
    if(log_level >= log::INFO) {
      log_manager->info(KERNEL_ID,
                        KERNEL_ID,
                        KERNEL_ID,
                        QString("Excess max in speed %1:%2->%3:%4 "
                                "on SID %5")
                          .arg(session->peerAddress().toString())
                          .arg(session->peerPort())
                          .arg(session->localAddress().toString())
                          .arg(session->localPort())
                          .arg(sid_sessions.value(session, QString()))
                          .toUtf8());
    }
  }
}


void KernelPrivate::sessionInitSrc() {
  smpp::sessions::ServerAsyncSession *src_session =
    qobject_cast<smpp::sessions::ServerAsyncSession *>(sender());

  if(!src_session) {
    return;
  }

  if(log_level >= log::INFO) {
    log_manager->info(KERNEL_ID,
                      KERNEL_ID,
                      KERNEL_ID,
                      QString("Source session %1:%2->%3:%4 init timeout")
                        .arg(src_session->peerAddress().toString())
                        .arg(src_session->peerPort())
                        .arg(src_session->localAddress().toString())
                        .arg(src_session->localPort())
                        .toUtf8());
  }
}

void KernelPrivate::errorSrc(smpp::sessions::ErrorCode code) {
  smpp::sessions::ServerAsyncSession *src_session =
    qobject_cast<smpp::sessions::ServerAsyncSession *>(sender());

  if(!src_session) {
    return;
  }

  smpp::sessions::ClientAsyncSession *dest_session =
    linked_sessions.value(src_session, 0);

  if(!dest_session) {
    return;
  }

  if(log_level >= log::WARNING) {
    log_manager->warning(KERNEL_ID,
                         KERNEL_ID,
                         KERNEL_ID,
                         QString("Source session %1:%2->%3:%4 "
                                 "error code (%5) message (%6)")
                           .arg(src_session->peerAddress().toString())
                           .arg(src_session->peerPort())
                           .arg(src_session->localAddress().toString())
                           .arg(src_session->localPort())
                           .arg(code)
                           .arg(src_session->errorString())
                           .toUtf8());
  }

  if(code == smpp::sessions::CONN_SOCK_ERR ||
     code == smpp::sessions::DISC_SOCK_ERR) {
    if(src_session->state() == smpp::sessions::CLOSED) {
      if(linked_sessions.remove(src_session) > 0) {
        sid_sessions.remove(src_session);
        client_sessions.remove(src_session);
        clearPacketsOnClose(src_session);
        delete src_session;
        delete dest_session;
      }
    } else {
      src_session->closeSession();
    }

    checkState();
  }
}


void KernelPrivate::incrementSessionCounter(const QString &system_id) {
  QString key(QString("%1/%2")
              .arg(system_id)
              .arg(QDateTime::currentDateTime().toString("MM-yyyy")));

  int count(counter_file->value(key, 0).toInt());

  count++;

  counter_file->setValue(key, count);

  counter_file->sync();
}

bool KernelPrivate::readConfig(QSettings *config) {
  bool res = true;

  int size(config->beginReadArray(CHANGE_SRC_ADDR_KEY));

  if(size == 0) {
    last_error = QString(NOT_FOUND_KEY_1).arg(CHANGE_SRC_ADDR_KEY);
    res = false;
    config->endArray();
    return res;
  }

  for(int i = 0; i < size; i++) {
    config->setArrayIndex(i);
    if(!config->contains(DEST_ADDR_KEY)) {
      last_error = QString(NOT_FOUND_KEY_1_2_3)
                   .arg(CHANGE_SRC_ADDR_KEY)
                   .arg(i)
                   .arg(DEST_ADDR_KEY);
      res = false;
      config->endArray();
      return res;
    }
    if(!config->contains(ADDR_BEFORE_KEY)) {
      last_error = QString(NOT_FOUND_KEY_1_2_3)
                   .arg(CHANGE_SRC_ADDR_KEY)
                   .arg(i)
                   .arg(ADDR_BEFORE_KEY);
      res = false;
      config->endArray();
      return res;
    }
    if(!config->contains(TON_BEFORE_KEY)) {
      last_error = QString(NOT_FOUND_KEY_1_2_3)
                   .arg(CHANGE_SRC_ADDR_KEY)
                   .arg(i)
                   .arg(TON_BEFORE_KEY);
      res = false;
      config->endArray();
      return res;
    }
    if(!config->contains(NPI_BEFORE_KEY)) {
      last_error = QString(NOT_FOUND_KEY_1_2_3)
                   .arg(CHANGE_SRC_ADDR_KEY)
                   .arg(i)
                   .arg(NPI_BEFORE_KEY);
      res = false;
      config->endArray();
      return res;
    }
    if(!config->contains(ADDR_AFTER_KEY)) {
      last_error = QString(NOT_FOUND_KEY_1_2_3)
                   .arg(CHANGE_SRC_ADDR_KEY)
                   .arg(i)
                   .arg(ADDR_AFTER_KEY);
      res = false;
      config->endArray();
      return res;
    }
    if(!config->contains(TON_AFTER_KEY)) {
      last_error = QString(NOT_FOUND_KEY_1_2_3)
                   .arg(CHANGE_SRC_ADDR_KEY)
                   .arg(i)
                   .arg(TON_AFTER_KEY);
      res = false;
      config->endArray();
      return res;
    }
    if(!config->contains(NPI_AFTER_KEY)) {
      last_error = QString(NOT_FOUND_KEY_1_2_3)
                   .arg(CHANGE_SRC_ADDR_KEY)
                   .arg(i)
                   .arg(NPI_AFTER_KEY);
      res = false;
      config->endArray();
      return res;
    }

    TranslateReglation *translate_reglation =
      new TranslateReglation(QRegExp(config->value(DEST_ADDR_KEY).toString()),
                             config->value(TON_BEFORE_KEY).toUInt(),
                             config->value(NPI_BEFORE_KEY).toUInt(),
                             config->value(ADDR_BEFORE_KEY).toString(),
                             config->value(TON_AFTER_KEY).toUInt(),
                             config->value(NPI_AFTER_KEY).toUInt(),
                             config->value(ADDR_AFTER_KEY).toString());


    table_translate.push_back(translate_reglation);
  }

  config->endArray();

  size = config->beginReadArray(CLIENTS_KEY);

  if(size == 0) {
    last_error = QString(NOT_FOUND_KEY_1).arg(CLIENTS_KEY);
    res = false;
    config->endArray();
    return res;
  }

  for(int i = 0; i < size; i++) {
    config->setArrayIndex(i);
    if(!config->contains(ACCESS_IP_KEY)) {
      last_error = QString(NOT_FOUND_KEY_1_2_3)
                   .arg(CLIENTS_KEY)
                   .arg(i)
                   .arg(ACCESS_IP_KEY);
      res = false;
      config->endArray();
      return res;
    }
    if(!config->contains(ACCESS_SID_KEY)) {
      last_error = QString(NOT_FOUND_KEY_1_2_3)
                   .arg(CLIENTS_KEY)
                   .arg(i)
                   .arg(ACCESS_SID_KEY);
      res = false;
      config->endArray();
      return res;
    }
    if(!config->contains(MAX_IN_SPEED_KEY)) {
      last_error = QString(NOT_FOUND_KEY_1_2_3)
                   .arg(CLIENTS_KEY)
                   .arg(i)
                   .arg(MAX_IN_SPEED_KEY);
      res = false;
      config->endArray();
      return res;
    }
    if(!config->contains(MAX_OUT_SPEED_KEY)) {
      last_error = QString(NOT_FOUND_KEY_1_2_3)
                   .arg(CLIENTS_KEY)
                   .arg(i)
                   .arg(MAX_OUT_SPEED_KEY);
      res = false;
      config->endArray();
      return res;
    }
    if(!config->contains(MAX_IN_TRANS_KEY)) {
      last_error = QString(NOT_FOUND_KEY_1_2_3)
                   .arg(CLIENTS_KEY)
                   .arg(i)
                   .arg(MAX_IN_TRANS_KEY);
      res = false;
      config->endArray();
      return res;
    }
    if(!config->contains(MAX_OUT_TRANS_KEY)) {
      last_error = QString(NOT_FOUND_KEY_1_2_3)
                   .arg(CLIENTS_KEY)
                   .arg(i)
                   .arg(MAX_OUT_TRANS_KEY);
      res = false;
      config->endArray();
      return res;
    }

    Client *client = new Client(
      QRegExp(config->value(ACCESS_IP_KEY).toString()),
      QRegExp(config->value(ACCESS_SID_KEY).toString()),
      config->value(MAX_IN_SPEED_KEY).toInt(),
      config->value(MAX_OUT_SPEED_KEY).toInt(),
      config->value(MAX_IN_TRANS_KEY).toInt(),
      config->value(MAX_OUT_TRANS_KEY).toInt());

    clients.push_back(client);
  }

  config->endArray();

  config->beginGroup(SERVER_KEY);

  if(!config->contains(LOG_LEVEL_KEY)) {
    last_error = QString(NOT_FOUND_KEY_1_2).arg(SERVER_KEY).arg(LOG_LEVEL_KEY);
    res = false;
    config->endGroup();
    return res;
  }
  if(!config->contains(LISTEN_HOST_KEY)) {
    last_error = QString(NOT_FOUND_KEY_1_2)
                  .arg(SERVER_KEY).arg(LISTEN_HOST_KEY);
    res = false;
    config->endGroup();
    return res;
  }
  if(!config->contains(LISTEN_PORT_KEY)) {
    last_error = QString(NOT_FOUND_KEY_1_2)
                  .arg(SERVER_KEY).arg(LISTEN_PORT_KEY);
    res = false;
    config->endGroup();
    return res;
  }
  if(!config->contains(SMSC_HOST_KEY)) {
    last_error = QString(NOT_FOUND_KEY_1_2).arg(SERVER_KEY).arg(SMSC_HOST_KEY);
    res = false;
    config->endGroup();
    return res;
  }
  if(!config->contains(SMSC_PORT_KEY)) {
    last_error = QString(NOT_FOUND_KEY_1_2).arg(SERVER_KEY).arg(SMSC_PORT_KEY);
    res = false;
    config->endGroup();
    return res;
  }
  if(!config->contains(STAT_FILE_KEY)) {
    last_error = QString(NOT_FOUND_KEY_1_2).arg(SERVER_KEY).arg(STAT_FILE_KEY);
    res = false;
    config->endGroup();
    return res;
  }
#ifdef WITH_ZABBIX
  if(!config->contains(ZABBIX_IP_KEY)) {
    last_error = QString(NOT_FOUND_KEY_1_2).arg(SERVER_KEY).arg(ZABBIX_IP_KEY);
    res = false;
    config->endGroup();
    return res;
  }
  if(!config->contains(ZABBIX_PORT_KEY)) {
    last_error = QString(NOT_FOUND_KEY_1_2).arg(SERVER_KEY).arg(ZABBIX_PORT_KEY);
    res = false;
    config->endGroup();
    return res;
  }
  if(!config->contains(ZABBIX_TIMEOUT_KEY)) {
    last_error = QString(NOT_FOUND_KEY_1_2)
                  .arg(SERVER_KEY).arg(ZABBIX_TIMEOUT_KEY);
    res = false;
    config->endGroup();
    return res;
  }
#endif
  log_level = (log::LogLevel)config->value(LOG_LEVEL_KEY).toInt();
  server_addr = QHostAddress(config->value(LISTEN_HOST_KEY).toString());
  server_port = config->value(LISTEN_PORT_KEY).toUInt();
  smsc_addr = QHostAddress(config->value(SMSC_HOST_KEY).toString());
  smsc_port = config->value(SMSC_PORT_KEY).toUInt();
  stat_file = new QFile(config->value(STAT_FILE_KEY).toString());
#ifdef WITH_ZABBIX
  zabbix_addr = QHostAddress(config->value(ZABBIX_IP_KEY).toString());
  zabbix_port = config->value(ZABBIX_PORT_KEY).toUInt();
  zabbix_time = config->value(ZABBIX_TIMEOUT_KEY).toInt();
#endif

  config->endGroup();

  return res;
}

void KernelPrivate::reloadConfig(QSettings *config) {
  if(log_level >= log::INFO) {
    log_manager->info(KERNEL_ID,
                      KERNEL_ID,
                      KERNEL_ID,
                      QString("Reloading config...").toUtf8());
  }
  server->close();
  if(!clients.isEmpty()) {
    qDeleteAll(clients);
    clients.clear();
  }
  if(stat_file) {
    stat_file->close();
    delete stat_file;
    stat_file = 0;
  }
  stopStatTimer();
#ifdef WITH_ZABBIX
  stopZabbixTimer();
#endif

  bool res = readConfig(config);

  if(!res) {
    if(log_level >= log::FATAL) {
      log_manager->fatal(KERNEL_ID,
                         KERNEL_ID,
                         KERNEL_ID,
                         QString("Reload config error (%1)!")
                           .arg(last_error)
                           .toUtf8());
    }
    delete config;
    return stop();
  }

  res = stat_file->open(QIODevice::WriteOnly);

  if(!res) {
    if(log_level >= log::FATAL) {
      log_manager->fatal(KERNEL_ID,
                         KERNEL_ID,
                         KERNEL_ID,
                         QString("Reload config error (%1)!")
                           .arg(stat_file->errorString())
                           .toUtf8());
    }
    delete config;
    return stop();
  }

  res = server->listen(server_addr, server_port);

  if(res) {
    startStatTimer();
#ifdef WITH_ZABBIX
    startZabbixTimer();
#endif
    if(log_level >= log::INFO) {
      log_manager->info(KERNEL_ID,
                        KERNEL_ID,
                        KERNEL_ID,
                        QString("Reload config success!").toUtf8());
    }
  } else {
    if(log_level >= log::FATAL) {
      log_manager->fatal(KERNEL_ID,
                         KERNEL_ID,
                         KERNEL_ID,
                         QString("Reload config error (%1)!")
                           .arg(server->errorString())
                           .toUtf8());
    }
    stop();
  }

  delete config;
}

void KernelPrivate::setState(State s) {
  if(state == s)
    return;

  switch(s) {
    case STOPING:
      if(state == STOPED) {
        return;
      } else {
        if(log_level >= log::INFO) {
          log_manager->info(KERNEL_ID,
                            KERNEL_ID,
                            KERNEL_ID,
                            QString("Stoping").toUtf8());
        }
        server->close();
        for(QHash<smpp::sessions::ServerAsyncSession *,
            smpp::sessions::ClientAsyncSession *>::iterator it =
              linked_sessions.begin();
            it != linked_sessions.end();
            it++) {
          it.key()->closeSession();
        }
      }
      break;
    case STARTING:
      if(state == STARTED) {
        return;
      } else {
        if(log_level >= log::INFO) {
          log_manager->info(KERNEL_ID,
                            KERNEL_ID,
                            KERNEL_ID,
                            QString("Starting").toUtf8());
        }
      }
      break;
    case STOPED:
      if(log_level >= log::INFO) {
        log_manager->info(KERNEL_ID,
                          KERNEL_ID,
                          KERNEL_ID,
                          QString("Stoped").toUtf8());
      }
      break;
    case STARTED:
      if(log_level >= log::INFO) {
        log_manager->info(KERNEL_ID,
                          KERNEL_ID,
                          KERNEL_ID,
                          QString("Started").toUtf8());
      }
      break;
  }

  state = s;

  emit parent->stateChanged(state);
}

void KernelPrivate::checkState() {
  switch(state) {
    case STOPING:
      if(linked_sessions.isEmpty()) {
#ifdef WITH_ZABBIX
        stopZabbixTimer();
#endif
        stopStatTimer();
        writeStat();
        setState(STOPED);
      }
      break;
    case STARTING:
      if(!stat_file->open(QIODevice::WriteOnly)) {
        if(log_level >= log::ERROR) {
          log_manager->error(KERNEL_ID,
                             KERNEL_ID,
                             KERNEL_ID,
                             QString("No open stat file on %1:%2 error (%3)")
                               .arg(server_addr.toString())
                               .arg(server_port)
                               .arg(stat_file->errorString())
                               .toUtf8());
        }
        setState(STOPED);
        break;
      }
      if(server->listen(server_addr, server_port)) {
        startStatTimer();
#ifdef WITH_ZABBIX
        startZabbixTimer();
#endif
        if(log_level >= log::INFO) {
          log_manager->info(KERNEL_ID,
                            KERNEL_ID,
                            KERNEL_ID,
                            QString("Listen server on %1:%2")
                            .arg(server_addr.toString())
                            .arg(server_port)
                            .toUtf8());
        }
        setState(STARTED);
      } else {
        if(log_level >= log::ERROR) {
          log_manager->error(KERNEL_ID,
                             KERNEL_ID,
                             KERNEL_ID,
                             QString("Not listen server on %1:%2 error (%3)")
                               .arg(server_addr.toString())
                               .arg(server_port)
                               .arg(server->errorString())
                               .toUtf8());
        }
        setState(STOPED);
      }
      break;
    default:
      break;
  }
}

QString KernelPrivate::stateToString(
  smpp::sessions::SessionStates state) const {
  switch(state) {
    case smpp::sessions::CLOSED:
      return QString("CLOSED");
    case smpp::sessions::OPEN:
      return QString("OPEN");
    case smpp::sessions::BOUND_TX:
      return QString("BOUND_TX");
    case smpp::sessions::BOUND_RX:
      return QString("BOUND_RX");
    case smpp::sessions::BOUND_TRX:
      return QString("BOUND_TRX");
    case smpp::sessions::UNBOUND:
      return QString("UNBOUND");
    case smpp::sessions::OUTBOUND:
      return QString("OUTBOUND");
    default:
      return QString();
  }
}

void KernelPrivate::startStatTimer() {
  if(stat_timer == -1) {
    stat_timer = startTimer(stat_time);
  }
}

void KernelPrivate::stopStatTimer() {
  if(stat_timer != -1) {
    killTimer(stat_timer);
    stat_timer = -1;
  }
}
#ifdef WITH_ZABBIX
void KernelPrivate::startZabbixTimer() {
  if(zabbix_timer == -1 && zabbix_time > 0) {
    zabbix_timer = startTimer(zabbix_time);
    zabbix = new ZabbixWriter(this, zabbix_addr, zabbix_port);
    connect(this, SIGNAL(writeZabbix(const QByteArray &)),
            zabbix, SLOT(write(const QByteArray &)));
    zabbix->start();
  }
}

void KernelPrivate::stopZabbixTimer() {
  if(zabbix_timer != -1) {
    killTimer(zabbix_timer);
    zabbix_timer = -1;
    zabbix->quit();
    zabbix->wait(1000);
    delete zabbix;
    zabbix = 0;
  }
}
#endif
void KernelPrivate::writeStat() {
  QString statistics_str;
  if(linked_sessions.size() > 0) {
    statistics_str +=
      "<table>"
        "<tr>"
          "<th>Client</th>"
          "<th>Client in speed</th>"
          "<th>Client out speed</th>"
          "<th>Client in trans</th>"
          "<th>Client out trans</th>"
          "<th>SID</th>"
          "<th>Server</th>"
          "<th>Server in speed</th>"
          "<th>Server out speed</th>"
          "<th>Server in trans</th>"
          "<th>Server out trans</th>"
        "</tr>";
    for(QHash<smpp::sessions::ServerAsyncSession *,
              smpp::sessions::ClientAsyncSession *>::iterator it =
          linked_sessions.begin();
        it != linked_sessions.end();
        it++) {
      smpp::sessions::ServerAsyncSession *c = it.key();
      smpp::sessions::ClientAsyncSession *s = it.value();
      statistics_str += QString(
        "<tr>"
          "<td>%1:%2->%3:%4(%5)</td>"
          "<td>%6</td>"
          "<td>%7</td>"
          "<td>%8</td>"
          "<td>%9</td>"
          "<td>%10</td>"
          "<td>%11:%12->%13:%14(%15)</td>"
          "<td>%16</td>"
          "<td>%17</td>"
          "<td>%18</td>"
          "<td>%19</td>"
        "</tr>")
        .arg(c->peerAddress().toString())
        .arg(c->peerPort())
        .arg(c->localAddress().toString())
        .arg(c->localPort())
        .arg(stateToString(c->state()))
        .arg(c->getNowInSpeed())
        .arg(c->getNowOutSpeed())
        .arg(c->getOpenedInTrans())
        .arg(c->getOpenedOutTrans())
        .arg(sid_sessions.value(c, QString()))
        .arg(s->localAddress().toString())
        .arg(s->localPort())
        .arg(s->peerAddress().toString())
        .arg(s->peerPort())
        .arg(stateToString(s->state()))
        .arg(s->getNowInSpeed())
        .arg(s->getNowOutSpeed())
        .arg(s->getOpenedInTrans())
        .arg(s->getOpenedOutTrans());

    }
    statistics_str += "</table>";
  }
  stat_file->resize(0);
  stat_file->write(statistics_str.toUtf8());
  stat_file->flush();
}
#ifdef WITH_ZABBIX
void KernelPrivate::writeZabbix() {
  if(sid_sessions.isEmpty()) {
    return;
  }

  QHash<QString, ZabbixStat *> stats;

  for(QHash<smpp::sessions::ServerAsyncSession *, QString>::iterator i =
        sid_sessions.begin();
      i != sid_sessions.end();
      i++) {
    smpp::sessions::ServerAsyncSession *session = i.key();
    QString sid(i.value());
    if(stats.contains(sid)) {
      stats[sid]->in_speed += session->getNowInSpeed();
      stats[sid]->out_speed += session->getNowOutSpeed();
    } else {
      ZabbixStat *stat =
        new ZabbixStat(session->getNowInSpeed(), session->getNowOutSpeed());

      stats.insert(sid, stat);
    }
  }

  QString stat_msg("{\"request\":\"agent data\",\"data\":[");

  uint timestamp(QDateTime::currentDateTime().toTime_t());

  bool first(true);

  for(QHash<QString, ZabbixStat *>::iterator i = stats.begin();
      i != stats.end();
      i++) {
    QString sid(i.key());
    ZabbixStat *stat = i.value();
    if(first) {
      first = false;
    } else {
      stat_msg += ",";
    }
    stat_msg += QString("{\"host\":\"Aggregators\","
                        "\"key\":\"aggr.%1.in_speed\","
                        "\"value\":%2,"
                        "\"clock\":%3},"
                        "{\"host\":\"Aggregators\","
                        "\"key\":\"aggr.%4.out_speed\","
                        "\"value\":%5,"
                        "\"clock\":%6}")
                  .arg(sid)
                  .arg(stat->in_speed)
                  .arg(timestamp)
                  .arg(sid)
                  .arg(stat->out_speed)
                  .arg(timestamp);
    delete stat;
  }

  stat_msg += QString("],\"clock\":%1}").arg(timestamp);

  QByteArray stat_msg_bytes(stat_msg.toUtf8());
  quint64 stat_mgs_size(stat_msg_bytes.size());

  QByteArray message;
  message.resize(13 + stat_mgs_size);
  int pos(0);

  message[pos++] = 'Z';
  message[pos++] = 'B';
  message[pos++] = 'X';
  message[pos++] = 'D';
  message[pos++] = 0x01;

  char *c_pt(reinterpret_cast<char *>(&stat_mgs_size));

  message[pos++] = c_pt[0];
  message[pos++] = c_pt[1];
  message[pos++] = c_pt[2];
  message[pos++] = c_pt[3];
  message[pos++] = c_pt[4];
  message[pos++] = c_pt[5];
  message[pos++] = c_pt[6];
  message[pos++] = c_pt[7];

  message.insert(pos, stat_msg_bytes);

  emit writeZabbix(message);
}
#endif
void KernelPrivate::clearPacketsOnClose(
  smpp::sessions::ServerAsyncSession *src_session) {
  if(!src_session) {
    return;
  }

  for(int i = 0; i < packets_on_close[src_session].size(); i++) {
    smpp::pdu::Header *header = packets_on_close[src_session][i];
    switch(header->getCommandId()) {
      case smpp::parameters::command_id::ENQUIRE_LINK:
        delete reinterpret_cast<smpp::pdu::EnquireLink *>(header);
        break;
      case smpp::parameters::command_id::BIND_RECEIVER:
        delete reinterpret_cast<smpp::pdu::BindReceiver *>(header);
        break;
      case smpp::parameters::command_id::BIND_TRANSCEIVER:
        delete reinterpret_cast<smpp::pdu::BindTransceiver *>(header);
        break;
      case smpp::parameters::command_id::BIND_TRANSMITTER:
        delete reinterpret_cast<smpp::pdu::BindTransmitter *>(header);
        break;
      default:
        break;
    }
  }

  packets_on_close.remove(src_session);
}

Kernel::Kernel(log::LogManager *log_manager) :
  QObject(0), p(new KernelPrivate(this, log_manager)) {

}

Kernel::~Kernel() {
  if(p) {
    delete p;
    p = 0;
  }
}

bool Kernel::readConfig(QSettings *config) {
  return p->readConfig(config);
}

void Kernel::reloadConfig(QSettings *config) {
  emit p->sigReloadConfig(config);
}

void Kernel::start() {
  p->start();
}

void Kernel::stop() {
  emit p->sigStop();
}

QString Kernel::getLastError() const {
  return p->last_error;
}

} // namespace smstranslator
